package com.info33.platform.system.service.impl;

import cn.hutool.core.img.ImgUtil;
import cn.hutool.extra.qrcode.QrCodeUtil;
import cn.hutool.extra.qrcode.QrConfig;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.info33.platform.common.MQservice.IMessageService;
import com.info33.platform.common.enums.LoginType;
import com.info33.platform.common.exception.BaseException;
import com.info33.platform.common.mvc.vo.Result;
import com.info33.platform.common.util.MQConstantUtil;
import com.info33.platform.common.util.MQMessageUtil;
import com.info33.platform.common.util.PasswordUtil;
import com.info33.platform.common.util.RedisUtils;
import com.info33.platform.config.shiro.token.UserToken;
import com.info33.platform.system.dao.SysTenantUserDao;
import com.info33.platform.system.dao.SysUserDao;
import com.info33.platform.system.dto.SysUserDTO;
import com.info33.platform.system.entity.SysTenantUser;
import com.info33.platform.system.entity.SysUser;
import com.info33.platform.system.service.SysLoginService;
import com.info33.platform.system.service.SysTenantService;
import com.info33.platform.system.util.enums.QrLoginStatusEnum;
import com.info33.platform.system.vo.SysTenantUserVO;
import com.info33.platform.system.vo.SysTenantVO;
import com.info33.platform.system.vo.SysUserVO;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author Administrator
 */
@Service
public class SysLoginServiceImpl implements SysLoginService {

    @Autowired
    private RedisUtils redisUtils;
    @Autowired
    private SysTenantUserDao sysTenantUserDao;
    @Autowired
    private SysTenantService sysTenantService;
    @Autowired
    private SysUserDao sysUserDao;

    /**
     * 定义新对象，避免 shiro 当前用户属性被变更，同时去除敏感字段
     *
     * @param token-
     * @return -
     */
    public Result<Object> excludeSensitivityInfo(String token, SysUserVO vo) {
        SysUserVO returnVo = new SysUserVO();
        // 用户信息
        returnVo.setToken(token);
        returnVo.setId(vo.getId());
        returnVo.setTrueName(vo.getTrueName());
        returnVo.setHeadImg(vo.getHeadImg());
        returnVo.setMobile(vo.getMobile());
        returnVo.setLoginName(vo.getLoginName());
        // 租户用户信息
        SysTenantUserVO tenantUserVO = new SysTenantUserVO();
        tenantUserVO.setId(vo.getSysTenantUserVO().getId());
        tenantUserVO.setMainColor(vo.getSysTenantUserVO().getMainColor());
        tenantUserVO.setUserType(vo.getSysTenantUserVO().getUserType());
        returnVo.setSysTenantUserVO(tenantUserVO);
        // 租户信息
        SysTenantVO tenantVO = new SysTenantVO();
        tenantVO.setType(vo.getTenant().getType());
        returnVo.setTenant(tenantVO);
        return Result.ok("登录成功", returnVo);
    }

    @Override
    public Result<Object> passwordLogin(SysUserDTO dto) {
        // 局部变量
        redisUtils.checkPwdErrCount(dto.getLoginName(), LoginType.SYS);
        // 若是开发环境，域名不存在则重载
        dto.getSysTenantDTO().setUrl(sysTenantService.devUrl(dto.getSysTenantDTO().getUrl()));
        try {
            Subject subject = SecurityUtils.getSubject();
            subject.login(new UserToken(dto.getLoginName(), dto.getPassword(), dto.getSysTenantDTO().getUrl(), LoginType.SYS_PASSWORD));
            SysUserVO vo = (SysUserVO) subject.getPrincipal();
            redisUtils.clearPwdErrCount(dto.getLoginName(), LoginType.SYS);
            // 更新该用户在此租户下的登录时间
            SysTenantUser entity = new SysTenantUser();
            entity.setId(vo.getSysTenantUserVO().getId());
            entity.setLoginTime(new Date());
            entity.setLoginType(LoginType.SYS_PASSWORD);
            sysTenantUserDao.updateById(entity);

            subject.getSession().setAttribute("ID",vo.getSysTenantUserVO().getId());

            return excludeSensitivityInfo(subject.getSession().getId().toString(), vo);
        } catch (UnknownAccountException e) {
            return Result.error(e.getMessage());
        } catch (LockedAccountException e) {
            return Result.error("登录失败，该用户已被冻结");
        } catch (IncorrectCredentialsException e) {
            return Result.error(redisUtils.addPwdErr(dto.getLoginName(), LoginType.SYS));
        } catch (AuthenticationException e) {
            return Result.error("认证失败");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error(Result.RESULT_ERROR_PUBLIC_MSG);
        }
    }

    @Override
    public Result<Object> messageLogin(SysUserDTO dto) {
        dto.getSysTenantDTO().setUrl(sysTenantService.devUrl(dto.getSysTenantDTO().getUrl()));
        redisUtils.checkCodeByMobile(dto.getMobile(), dto.getCode());
        try {
            Subject subject = SecurityUtils.getSubject();
            subject.login(new UserToken(dto.getMobile(), dto.getCode(), dto.getSysTenantDTO().getUrl(), LoginType.SYS_MESSAGE));
            SysUserVO vo = (SysUserVO) subject.getPrincipal();
            // 更新该用户在此租户下的登录时间
            SysTenantUser entity = new SysTenantUser();
            entity.setId(vo.getSysTenantUserVO().getId());
            entity.setLoginTime(new Date());
            entity.setLoginType(LoginType.SYS_MESSAGE);
            sysTenantUserDao.updateById(entity);
            return excludeSensitivityInfo(subject.getSession().getId().toString(), vo);
        } catch (UnknownAccountException e) {
            return Result.error(e.getMessage());
        } catch (LockedAccountException e) {
            return Result.error("登录失败，该用户已被冻结");
        } catch (AuthenticationException e) {
            return Result.error("认证失败");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error(Result.RESULT_ERROR_PUBLIC_MSG);
        }
    }

    @Override
    public Result<Object> messagePush(SysUserDTO dto) {
        dto.getSysTenantDTO().setUrl(sysTenantService.devUrl(dto.getSysTenantDTO().getUrl()));
        SysTenantUserVO tenantUserVO = sysTenantUserDao.registered(dto);
        if (ObjectUtils.isEmpty(tenantUserVO)) {
            return Result.error("该租户不存在，不可使用此功能");
        }
        if (Boolean.FALSE.equals(tenantUserVO.getTenantVO().getActivate())) {
            return Result.error("该租户未激活，不可使用此功能");
        }
        if (ObjectUtils.isEmpty(tenantUserVO.getUserId())) {
            return Result.error("该用户不存在，不可使用此功能");
        }
        if (Boolean.TRUE.equals(tenantUserVO.getLocked())) {
            return Result.error("该用户已被冻结，不可使用此功能");
        }
        // 发送验证码
        return Result.ok(redisUtils.pushCodeByMobile(dto.getMobile()));
    }

    @Override
    public Result<Object> generateQr() {
        try {
            String content = System.currentTimeMillis() + UUID.randomUUID().toString().replace("-", "");
            String base64 = QrCodeUtil.generateAsBase64(
                    content,
                    new QrConfig().setImg(new ClassPathResource("static/qrlogo.png").getFile()),
                    ImgUtil.IMAGE_TYPE_JPG);
            // 将 content 存入 Redis 中缓存
            redisUtils.saveQrKey(content, QrLoginStatusEnum.GENERATEQRCODE);
            Map<String, String> map = new HashMap<String, String>();
            map.put("uuid", content);
            map.put("img", base64);
            return Result.ok(map);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error();
        }
    }

    @Override
    public Result<Object> qrScan(String uuid) {
        // 校验二维码是否过期
        redisUtils.checkQrKey(uuid);
        // 重置二维码状态为已扫描
        redisUtils.saveQrKey(uuid, QrLoginStatusEnum.SCANQRCODE);
        return Result.ok();
    }

    @Override
    public Result<Object> qrLogin(String uuid) {
        redisUtils.checkQrKey(uuid);
        try {
            // 获取当前登录人的 token
            Subject subject = SecurityUtils.getSubject();
            SysUserVO sysUserVO = (SysUserVO) subject.getPrincipal();
            if (ObjectUtils.isEmpty(sysUserVO.getSysTenantUserVO())) {
                return Result.error(Result.RESULT_ERROR_NO_POWER);
            }
            redisUtils.saveQrContent(uuid, subject.getSession().getId().toString());
            // 更新二维码登录状态为确认登录
            redisUtils.saveQrKey(uuid, QrLoginStatusEnum.CONFIRMQRCODE);
            return Result.ok("扫码成功");
        } catch (UnknownAccountException e) {
            return Result.error(e.getMessage());
        } catch (LockedAccountException e) {
            return Result.error("登录失败，该用户已被冻结");
        } catch (AuthenticationException e) {
            return Result.error("认证失败");
        } catch (Exception e) {
            e.printStackTrace();
            return Result.error(Result.RESULT_ERROR_PUBLIC_MSG);
        }
    }

    @Override
    public Result<Object> qrPolling(String uuid) {
        QrLoginStatusEnum value = (QrLoginStatusEnum) redisUtils.checkQrKey(uuid);
        Map<String, Object> map = new HashMap<String, Object>();
        switch (value) {
            case GENERATEQRCODE:
                map.put("status", value);
                break;
            case SCANQRCODE:
                map.put("status", value);
                break;
            case CONFIRMQRCODE:
                map.put("status", value);
                map.put("token", redisUtils.getQrContent(uuid));
                break;
            default:
                map.put("status", QrLoginStatusEnum.QRCODEERROR);
                break;
        }
        return Result.ok(map);
    }

    @Override
    public Result<Object> forgetPassword(SysUserDTO dto) {
        if (!dto.getPassword().equals(dto.getConfirmPassword())) {
            return Result.error("确认密码与密码不一致");
        }
        // 校验验证码
        redisUtils.checkCodeByMobile(dto.getMobile(), dto.getCode());
        Map<String, String> map = PasswordUtil.passwordMd5(dto.getPassword());
        SysUser sysUser = sysUserDao.selectOne(new QueryWrapper<SysUser>().eq("mobile", dto.getMobile()));
        sysUser.setPassword(map.get("password"));
        sysUser.setSalt(map.get("salt"));
        return sysUserDao.updateById(sysUser) > 0 ? Result.ok() : Result.error();
    }


}